package com.springboot.security.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.springboot.security.filter.CustomAccessDecisionManager;
import com.springboot.security.filter.CustomFilterInvocationSecurityMetadataSource;
import com.springboot.security.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * 基于注解的权限控制 @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
 * prePostEnabled = true 会解锁 @PreAuthorize 和 @PostAuthorize
 * securedEnabled = true 会解锁 @Secured注解
 * @author wangning
 * @create 2021-05-18 11:15
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

	@Autowired
	private UserService userService;
	/**
	 * 以下为明文存储密码的方式
	 */
//	@Bean
//	public PasswordEncoder passwordEncoder() {
//		return NoOpPasswordEncoder.getInstance();//无加密，测试用
//	}
//
//	@Override
//	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//		auth.inMemoryAuthentication()
//				.withUser("root").password("123").roles("ADMIN", "DBA")
//				.and()
//				.withUser("admin").password("123").roles("ADMIN", "USER")
//				.and()
//				.withUser("wang").password("123").roles("USER");
//
//	}



	/**
	 * 以下为使用
	 */
	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//		auth.inMemoryAuthentication()
//				.withUser("admin").password("$2a$10$XnVPrpoaUu/NBEcNr9788uilscPQ77FiOJrdqamXsypjYtwSnd1eC").roles("ADMIN", "USER")
//				.and()
//				.withUser("sang").password("$2a$10$XnVPrpoaUu/NBEcNr9788uilscPQ77FiOJrdqamXsypjYtwSnd1eC").roles("USER");

		auth.userDetailsService(userService);
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
//		http
//				//未登录处理
//				.exceptionHandling()
//				.authenticationEntryPoint((request, response, e) -> {
//					response.setContentType("application/json;charset=utf-8");
//					response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
//					response.getWriter().write("未登录");
////					response.getWriter()
////							.write(JSONObject.toJSONString(
////									RespMessage.build(false, HttpServletResponse.SC_UNAUTHORIZED, "未登录")));
//				})
//				//没有权限处理
//				.accessDeniedHandler((request, response, e) -> {
//					response.setContentType("application/json;charset=utf-8");
//					response.setStatus(HttpServletResponse.SC_FORBIDDEN);
//					response.getWriter().write("没有权限");
////					response.getWriter()
////							.write(JSONObject.toJSONString(
////									RespMessage.build(false, HttpServletResponse.SC_FORBIDDEN, "没有权限")));
//				});
		http
				.authorizeRequests()
//				.antMatchers("/admin/**").hasRole("admin")
//				.antMatchers("/user/**").access("hasAnyRole('admin', 'user')")
//				.antMatchers("/db/**").access("hasRole('dba')")
//				.anyRequest().authenticated()
				.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
					@Override
					public <O extends FilterSecurityInterceptor> O postProcess(O object) {
						object.setSecurityMetadataSource(cfisms());
						object.setAccessDecisionManager(cadm());
						return object;
					}
				})
				.and()
				//登录表单详细配置
				.formLogin()
//				.loginPage("/login_page")
				.loginProcessingUrl("/login")
//				.usernameParameter("username")
//				.passwordParameter("password")
				.successHandler(new AuthenticationSuccessHandler() {
					@Override
					public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException, ServletException {
						Object principal = auth.getPrincipal();
						response.setContentType("application/json;charset=utf-8");
						response.setStatus(200);
						PrintWriter out = response.getWriter();
						Map<String, Object> map = new HashMap<>();
						map.put("status", 200);
						map.put("msg", principal);
						ObjectMapper objectMapper = new ObjectMapper();
						out.write(objectMapper.writeValueAsString(map));
						out.flush();
						out.close();
					}
				})
				.failureHandler(new AuthenticationFailureHandler() {
					@Override
					public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
						response.setContentType("application/json;charset=utf-8");
						response.setStatus(401);
						PrintWriter out = response.getWriter();
						Map<String, Object> map = new HashMap<>();
						map.put("status", 401);
						if (exception instanceof LockedException) {
							map.put("msg", "账户被锁定，登陆失败！");
						} else if (exception instanceof BadCredentialsException) {
							map.put("msg", "账户名或密码输入错误，登陆失败！");
						} else if (exception instanceof DisabledException) {
							map.put("msg", "账户被禁用，登陆失败！");
						} else if (exception instanceof AccountExpiredException) {
							map.put("msg", "账户已过期，登陆失败！");
						} else if (exception instanceof CredentialsExpiredException) {
							map.put("msg", "密码已过期，登陆失败！");
						} else {
							map.put("msg", "登陆失败！");
						}
						ObjectMapper objectMapper = new ObjectMapper();
						out.write(objectMapper.writeValueAsString(map));
						out.flush();
						out.close();

					}
				})

				.permitAll()
				//注销登录配置
				.and()
				.logout()
				.logoutUrl("/logout")
				.clearAuthentication(true)//清除认证信息
				.invalidateHttpSession(true)//是否使session失效
				.addLogoutHandler(new LogoutHandler() {
					/**
					 * 可以在这里完成一些数据清除工作，例如 Cookie
					 */
					@Override
					public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {

					}
				})
				.logoutSuccessHandler(new LogoutSuccessHandler() {
					/**
					 * 这里处理注销成功的业务逻辑，例如返回一段 JSON 提示或者跳转登录页面
					 */
					@Override
					public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
						response.sendRedirect("/login");
					}
				})
				.and()
				//关闭Spring Security自带的csrf
				.csrf().disable()
				.cors();//允许跨域

	}

	@Bean
	public  AccessDecisionManager cadm() {
		return new CustomAccessDecisionManager();
	}

	@Bean
	public FilterInvocationSecurityMetadataSource cfisms() {
		return new CustomFilterInvocationSecurityMetadataSource();
	}

	/**
	 * 在案例中定义了三个角色，但是三个角色并不具备任何关系，一般来说角色是有关系的，
	 * 如 ROLE_admin 一般既有 admin 的权限，也有 user 的权限，那么需要如何配置呢？
	 * 我们只需要在 Spring Security 中提供一个 RoleHierarchy 即可，配置如下
	 * 或者使用xml进行配置，不过建议使用注解进行配置
	 * xml配置如下
	 * <bean id="roleVoter" class="org.springframework.security.access.vote.RoleHierarchyVoter">
	 *     <constructor-arg ref="roleHierarchy" />
	 * </bean>
	 * <bean id="roleHierarchy"
	 *         class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
	 *     <property name="hierarchy">
	 *         <value>
	 *             ROLE_ADMIN > ROLE_STAFF
	 *             ROLE_STAFF > ROLE_USER
	 *             ROLE_USER > ROLE_GUEST
	 *         </value>
	 *     </property>
	 * </bean>
	 * 注意：角色继承关系字符串必须使用’\n’换行，’>'前后必须有空格
	 * @return
	 */
	@Bean
	public RoleHierarchy roleHierarchy() {
		/*
		目前遗留问题：动态配置后发现角色继承不起效果了
		 */
		RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
		String hierarchy = "ROLE_dba > ROLE_admin" +
				"\n ROLE_admin > ROLE_user";
		roleHierarchy.setHierarchy(hierarchy);
		return roleHierarchy;
	}
}
